Native Drag Reordering
The ForEach component renders a dynamic list of child views. It is used to display collections, create editable lists, and enable system-standard interactions such as swipe-to-delete. It is fully integrated with the Scripting app’s Observable state system and mirrors the design of SwiftUI’s ForEach.
ForEach supports two usage modes:
- Deprecated mode:
count + itemBuilder - Recommended mode:
data: Observable<T[]> + builder
1. Type Definitions
ForEachDeprecatedProps (Not Recommended)
Property Details
count: number
Specifies how many items to render. The itemBuilder function will be called for indices from 0 to count - 1.
itemBuilder(index)
Builds a VirtualNode for each index.
onDelete(indices)
Enables system-standard swipe-to-delete when the ForEach is placed inside a List.
This callback is invoked after the row has already been removed from the list UI.
You must manually delete the corresponding items from your data source inside this callback.
onMove(indices, newOffset)
Enables drag-to-reorder behavior.
To disable item movement, pass null.
2. ForEachProps (Recommended)
Property Details
data: Observable<T[]>
An observable array of items.
Each item must contain a unique id: string.
Benefits of using Observable<T[]>:
- Automatic refresh when the collection changes
- Supports animation
- Behavior closer to SwiftUI’s
ForEach($items) - Integrates cleanly with
List,NavigationStack, and other components
builder(item, index)
Builds a VirtualNode for the element at the given index.
Important: You must provide a unique key (usually item.id) on the returned node.
editActions: "delete" | "move" | "all" | null
Defines the editing capabilities of the ForEach component.
When used inside a List, these actions automatically map to system-standard interactions.
3. ForEachComponent Interface
The component is generic and supports any item type containing an id.
4. Enabling System-Standard Deletion (Example)
When ForEach is placed inside a List, using data and builder will automatically activate swipe-to-delete. The only requirement is that each item has a unique id.
Example
5. Best Practices and Usage Guidelines
1. Prefer the data: Observable<T[]> API
This mode provides:
- Better performance
- Full type inference
- Proper list diffing and animations
- Consistent behavior with SwiftUI
2. Every item must have a unique id: string
This ensures:
- Correct diff computation
- Smooth animations
- Accurate deletion and movement behavior
3. Always define key={item.id} in the builder
Failing to do so may result in:
- Broken animations
- Incorrect row updates
- Misaligned delete/move behavior
4. To support editing, place ForEach inside a List
And optionally add an EditButton, for example:
Native drag reordering (iOS 27.0+)
The data-backed ForEach accepts reorderable?: boolean | { collectionID: string }. Put it inside a reorderContainer modifier. The default remains disabled; earlier systems ignore native reordering. The existing ReorderableForEach component retains its previous behavior.
reorderContainer accepts { isEnabled?: boolean, acrossCollections?: boolean, move: (difference: ReorderDifference) => void }. Defaults: enabled, single collection. A difference contains sources: string[] and a destination { position: "before", itemID: string } or { position: "end" }. For multiple collections set acrossCollections: true and provide a collectionID to every ForEach; the destination additionally contains collectionID. DropSession.reorderDestination uses the same destination shape.
Update your Observable data synchronously in move. Remove the source items, then insert them before the destination ID (or at the end), preserving the order of sources. For multiple collections update both source and destination collections. Keep IDs unique within the container. This is independent of editActions and does not auto-update the array.
